using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;

namespace Konst3d.Efficiency.Suite.SparsedBinary
{
    internal class LabelsStorage: IDisposable
    {
        #region Constants

        private const string    LABELS_FILE_EXT = "labels";
        private const string    INDEX_FILE_EXT  = "labels.index";

        #endregion // Constants

        #region Create/Open/Delete Methods

        public static LabelsStorage Create(string file, bool overwrite, ICryptographicEngine cryptographicEngine)
        {
            Contract.Requires(!String.IsNullOrEmpty(file));
            Contract.Requires(cryptographicEngine != null);

            LabelsTextFile labels = null;
            LabelsIndexFile indices = null;

            try
            {
                labels = LabelsTextFile.Create(String.Format("{0}.{1}", file, LABELS_FILE_EXT), overwrite, cryptographicEngine);
                indices = LabelsIndexFile.Create(String.Format("{0}.{1}", file, INDEX_FILE_EXT), overwrite);

                return new LabelsStorage(labels, indices, cryptographicEngine);
            }
            catch
            {
                if (labels != null)
                {
                    labels.Dispose();
                }

                if (indices != null)
                {
                    indices.Dispose();
                }

                throw;
            }
        }

        public static LabelsStorage Open(string file, ICryptographicEngine cryptographicEngine)
        {
            Contract.Requires(!String.IsNullOrEmpty(file));
            Contract.Requires(cryptographicEngine != null);

            string labelsFile = String.Format("{0}.{1}", file, LABELS_FILE_EXT);
            string indexFile = String.Format("{0}.{1}", file, INDEX_FILE_EXT);

            LabelsTextFile labels = null;
            LabelsIndexFile indices = null;

            try
            {
                labels = LabelsTextFile.Open(String.Format("{0}.{1}", file, LABELS_FILE_EXT), cryptographicEngine);
                indices = LabelsIndexFile.Open(String.Format("{0}.{1}", file, INDEX_FILE_EXT));

                return new LabelsStorage(labels, indices, cryptographicEngine);
            }
            catch
            {
                if (labels != null)
                {
                    labels.Dispose();
                }

                if (indices != null)
                {
                    indices.Dispose();
                }

                throw;
            }
        }

        public void Delete()
        {
            Contract.Assert(!_disposed);

            _labels.Delete();
            _indices.Delete();

            Dispose();
        }

        #endregion // Create/Open/Delete Methods

        #region Constructors/Finalizer

        private LabelsStorage(LabelsTextFile labels, LabelsIndexFile indices, ICryptographicEngine cryptographicEngine)
        {
            Contract.Requires(labels != null);
            Contract.Requires(indices != null);
            Contract.Requires(cryptographicEngine != null);

            _labels = labels;
            _indices = indices;
            _cryptographicEngine = cryptographicEngine;

            _cache.Add(-1, String.Empty);
        }

        ~LabelsStorage()
        {
            Dispose(false);
        }

        #endregion // Constructors/Finalizer

        #region Public Methods

        public string GetLabel(int id)
        {
            string result = null;

            if (_cache.ContainsKey(id))
            {
                result = _cache[id];
            }
            else
            {
                var entry = _indices.GetEntry(id);
                result = _labels.GetLabel(entry.Offset, entry.Used);

                if (!String.IsNullOrEmpty(result))
                {
                    _cache.Add(id, result);
                }
            }

            return result;
        }

        public int AddLabel(string label)
        {
            Contract.Assert(label.Length <= ushort.MaxValue);

            int id = -1;

            if (!String.IsNullOrEmpty(label))
            {
                int size = _cryptographicEngine.GetLength(label);
                id = _indices.FindEmptyEntry(size);

                if (id >= 0)
                {
                    var entry = _indices.GetEntry(id);

                    _labels.SetLabel(entry.Offset, label);
                    _indices.UpdateEntry(id, (ushort)size);
                }
                else
                {
                    var result = _labels.AddLabel(label);
                    id = _indices.AddEntry((uint)result.Offset, (ushort)size, (ushort)result.Capacity);
                }

                _cache.Add(id, label);
            }

            return id;
        }

        public void DeleteLabel(int id)
        {
            if (id >= 0)
            {
                _indices.UpdateEntry(id, 0);

                if (_cache.ContainsKey(id))
                {
                    _cache.Remove(id);
                }
            }
        }

        public int UpdateLabel(int id, string newValue)
        {
            Contract.Requires(newValue.Length <= ushort.MaxValue);

            int newID = id;

            if (id < 0)
            {
                newID = AddLabel(newValue);
            }
            else
            {
                if (String.IsNullOrEmpty(newValue))
                {
                    DeleteLabel(id);
                    newID = -1;

                    if (_cache.ContainsKey(id))
                    {
                        _cache.Remove(id);
                    }
                }
                else
                {
                    var entry = _indices.GetEntry(id);

                    if (entry.Reserved >= _cryptographicEngine.GetLength(newValue))
                    {
                        _labels.SetLabel(entry.Offset, newValue);
                        _indices.UpdateEntry(id, (ushort)_cryptographicEngine.GetLength(newValue));
                    }
                    else
                    {
                        DeleteLabel(id);
                        newID = AddLabel(newValue);
                    }

                    _cache[id] = newValue;
                }
            }

            return newID;
        }

        #endregion // Public Methods

        #region IDisposable Members

        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Dispose(true);
        }

        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (_labels != null)
                    {
                        _labels.Dispose();
                        _labels = null;
                    }

                    if (_indices != null)
                    {
                        _indices.Dispose();
                        _indices = null;
                    }

                    _cryptographicEngine = null;
                }

                _disposed = true;
            }
        }

        #endregion

        #region Fields

        private bool _disposed = false;
        private ICryptographicEngine _cryptographicEngine;
        private Dictionary<int, string> _cache = new Dictionary<int, string>();
        private LabelsIndexFile _indices;
        private LabelsTextFile _labels;

        #endregion // Fields
    }
}
